package com.alinesno.cloud.common.web.login.shiro.config;

import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.annotation.Order;

import com.alinesno.cloud.common.web.login.constants.LoginConfigurationBean;
import com.alinesno.cloud.common.web.login.filter.URLPathMatchingFilter;
import com.alinesno.cloud.common.web.login.shiro.AccountRealm;
import com.alinesno.cloud.common.web.login.shiro.KickoutSessionControlFilter;
import com.alinesno.cloud.common.web.login.shiro.RetryLimitHashedCredentialsMatcher;
import com.alinesno.cloud.common.web.login.shiro.logout.ShiroLogoutFilter;
import com.alinesno.cloud.common.web.login.shiro.redis.ShiroRedisCacheManager;

/**
 * shiro配置中心
 * 
 * @author WeiXiaoJin
 * @sine 2019年4月5日 上午10:59:15
 */
@Order(5)
@Configuration
public class ShiroConfiguration {

	private static final Logger log = LoggerFactory.getLogger(ShiroConfiguration.class);

	@Value("${alinesno.login.max-retry:5}")
	private int maxRetry = 5 ; 
	
	@Value("${alinesno.login.lock-time:5}")
	private int lockTime = 5 ; 
	
	/**
	 * Shiro的Web过滤器Factory 命名:shiroFilter<br />
	 * * * @param securityManager * @return
	 */
	@Bean(name = "shiroFilter")
	public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {

		String defaultLoginPath = LoginConfigurationBean.shiroLoginSubmitPath();
		String defaultSuccessPath = LoginConfigurationBean.shiroLoginSuccessPath();

		log.info("注入Shiro Web过滤器:{} , 默认登陆路径:{} , 登陆成功进入页面:{}", ShiroFilterFactoryBean.class, defaultLoginPath , defaultSuccessPath);
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

		// Shiro的核心安全接口,这个属性是必须的
		shiroFilterFactoryBean.setSecurityManager(securityManager);

		// 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面
		shiroFilterFactoryBean.setLoginUrl(defaultLoginPath);

		// 登录成功后要跳转的连接,逻辑也可以自定义，例如返回上次请求的页面
		shiroFilterFactoryBean.setSuccessUrl(defaultSuccessPath);

		// 用户访问未对其授权的资源时,所显示的连接
		shiroFilterFactoryBean.setUnauthorizedUrl("/403");

		/*
		 * 定义shiro过滤器,例如实现自定义的FormAuthenticationFilter，需要继承FormAuthenticationFilter
		 * **本例中暂不自定义实现，在下一节实现验证码的例子中体现
		 */
		LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
		filtersMap.put("logout", shiroLogoutFilter()); // 配置自定义登出 覆盖 logout 之前默认的LogoutFilter

		// 限制同一帐号同时在线的个数。
		filtersMap.put("kickout", kickoutSessionControlFilter());

		// URL访问权限配置
		filtersMap.put("requestURL", urlPathMatchingFilter());

		shiroFilterFactoryBean.setFilters(filtersMap);

		/*
		 * 定义shiro过滤链 Map结构 *
		 * Map中key(xml中是指value值)的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的
		 * * anon：它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 *
		 * authc：该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.
		 * FormAuthenticationFilter
		 */
		Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();

		// <!-- 过滤链定义，从上向下顺序执行，一般将 /**放在最为下边 -->:这是一个坑呢，一不小心代码就不好使了;
		// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
		filterChainDefinitionMap.put(defaultLoginPath, "anon");// anon 可以理解为不拦截

		// 静态资源
		filterChainDefinitionMap.put("/asserts/**", "anon");
		filterChainDefinitionMap.put("/static/**", "anon");
		filterChainDefinitionMap.put("/resources/**", "anon");
		filterChainDefinitionMap.put("/error/**", "anon");

		// 静态页面
		filterChainDefinitionMap.put("/pages/**", "anon");
		filterChainDefinitionMap.put("/public/**", "anon");
		
		// actuator 监控
		filterChainDefinitionMap.put("/actuator/**", "anon");

		filterChainDefinitionMap.put("/", "anon");
//		filterChainDefinitionMap.put("/*.txt", "anon");
//		filterChainDefinitionMap.put("/*.html", "anon");

		filterChainDefinitionMap.put("/**", "authc");

		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;
	}

//	/**
//	 * 限制同一账号登录同时登录人数控制
//	 *
//	 * @return
//	 */
//	public KickoutSessionControlFilter kickoutSessionControlFilter() {
//		kickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
//		// 使用cacheManager获取相应的cache来缓存用户登录的会话；用于保存用户—会话之间的关系的；
//		// 这里我们还是用之前shiro使用的redisManager()实现的cacheManager()缓存管理
//		// 也可以重新另写一个，重新配置缓存时间之类的自定义缓存属性
//		kickoutSessionControlFilter.setCacheManager(cacheManager());
//		// 用于根据会话ID，获取会话进行踢出操作的；
//		kickoutSessionControlFilter.setSessionManager(sessionManager());
//		// 是否踢出后来登录的，默认是false；即后者登录的用户踢出前者登录的用户；踢出顺序。
//		kickoutSessionControlFilter.setKickoutAfter(false);
//		// 同一个用户最大的会话数，默认1；比如2的意思是同一个用户允许最多同时两个人登录；
//		kickoutSessionControlFilter.setMaxSession(1);
//		// 被踢出后重定向到的地址；
//		kickoutSessionControlFilter.setKickoutUrl("kickout");
//		return kickoutSessionControlFilter;
//	}

	@Bean("kickoutSessionControlFilter")
	public Filter kickoutSessionControlFilter() {
		KickoutSessionControlFilter k = new KickoutSessionControlFilter() ; 
		String defaultLoginPath = LoginConfigurationBean.shiroLoginSubmitPath();

		k.setSessionManager(getDefaultWebSessionManager());
		k.setCacheManager(redisCacheManager());
		k.setKickoutAfter(false);
		k.setMaxSession(1);
		k.setKickoutUrl(defaultLoginPath+"?kickout=1"); 
		
		return k ;
	}

	/**
	 * 请求链接过滤
	 * 
	 * @return
	 */
	private Filter urlPathMatchingFilter() {
		return new URLPathMatchingFilter();
	}
	
	//配置shiro session 的一个管理器
    @Bean(name = "sessionManager")
    public DefaultWebSessionManager getDefaultWebSessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        
//        // 设置session过期时间
//        sessionManager.setGlobalSessionTimeout(60*60*1000);
//        // 请注意看代码
//        sessionManager.setSessionDAO(getMemorySessionDAO());
        
        return sessionManager;
    }

	/**
	 * 配置LogoutFilter
	 * 
	 * @return
	 */
	public ShiroLogoutFilter shiroLogoutFilter() {
		ShiroLogoutFilter shiroLogoutFilter = new ShiroLogoutFilter();

		// 配置登出后重定向的地址，等出后配置跳转到登录接口
		shiroLogoutFilter.setRedirectUrl(LoginConfigurationBean.shiroLoginPath());
		return shiroLogoutFilter;
	}

	@Bean
	public EhCacheManager ehCacheManager() {
		EhCacheManager cacheManager = new EhCacheManager();
		return cacheManager;
	}
	
	/**
     * 配置密码比较器
     * @return
     */
    @Bean("retryLimitHashedCredentialsMatcher")
    public RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher(){
        RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher();
        retryLimitHashedCredentialsMatcher.setMaxRetryNum(maxRetry);
        return retryLimitHashedCredentialsMatcher;
    }


	@Bean
	public CacheManager redisCacheManager() {
		ShiroRedisCacheManager redisCacheManager = new ShiroRedisCacheManager();
		return redisCacheManager;
	}

	/**
	 * 不指定名字的话，自动创建一个方法名第一个字母小写的bean * @Bean(name = "securityManager") * @return
	 */
	@Bean
	public SecurityManager securityManager() {
		log.info("注入Shiro Web securityManager 过滤器:{}", ShiroFilterFactoryBean.class);
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setRealm(shiroRealm());
		securityManager.setCacheManager(redisCacheManager());
		return securityManager;
	}

	/**
	 * Shiro生命周期处理器 * @return
	 */
	@Bean
	public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

	/**
	 * ShiroRealm，这是个自定义的认证类，继承自AuthorizingRealm， 负责用户的认证和权限的处理，可以参考JdbcRealm的实现。
	 */
	@Bean(name = "shiroRealm")
	@DependsOn({"lifecycleBeanPostProcessor"})
	public AccountRealm shiroRealm() {
		AccountRealm realm = new AccountRealm();
		
		// 设置密码比较器,进行登陆限制
		realm.setCredentialsMatcher(retryLimitHashedCredentialsMatcher());
		
		return realm ;
	}

	/**
	 * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
	 * *
	 * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
	 * * @return
	 */
	@Bean
	@DependsOn({ "lifecycleBeanPostProcessor" })
	public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
		advisorAutoProxyCreator.setProxyTargetClass(true);
		return advisorAutoProxyCreator;
	}

	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}
}